package domein;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.logging.Logger;

import domein.models.BestandenListModel;
import domein.models.DownloadsTableModel;
import domein.models.PeersListModel;
import domein.models.Subject;
import domein.models.Observer;

/**
 * 
 * @author rensbonnez
 * PeerController will insure that the host can only make 1 client.
 * Further it will delegate the peerClient.
 *
 */

public class PeerController implements Subject
{
	public static Logger logger = Logger.getLogger("MyLog");
	private static PeerController peerController;
	private HashMap<InetAddress, Peer> peers;
	private Peer localHost;
	private PeerServer peerServer;
	private FileServer fileServer;
	private List<PeerFile> localHostFiles;
	private List<DownloadFile> downloadFiles;
	private HashSet<PeerFile> downloadedPeerFiles;
	private int countForFiles = 0;
	private ArrayList<Observer> observers;
	private Boolean serverIsRunning;
	
	private BestandenListModel bestandListModel;
	private DownloadsTableModel downloadTableModel;
	private PeersListModel peerListModel;
	
	private PeerController()
	{
		observers = new ArrayList<Observer>();
		peers = new HashMap<InetAddress, Peer>();
		peerServer = new PeerServer(this);
		fileServer = new FileServer();
		localHostFiles = new ArrayList<PeerFile>();
		downloadFiles = new ArrayList<DownloadFile>();
		downloadedPeerFiles = new HashSet<PeerFile>();
		
		try {
			localHost = new Peer(InetAddress.getLocalHost(), InetAddress.getLocalHost().getHostName(), getLocalHostFileList());
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
	}
	
	//SingletonPattern
	public static PeerController getInstance()
	{
		if(peerController == null)
		{
			synchronized (PeerController.class) {
				peerController = new PeerController();
			}
		}
		
		return peerController;
	}
	
	public void startServers()
	{
		bestandListModel = new BestandenListModel(this);
		downloadTableModel = new DownloadsTableModel(this);
		peerListModel = new PeersListModel(this);
		
		setServerIsRunning(true);
		refreshLocalHostFileList();
		
		getPeerServer().startServer();
		getFileServer().startServer();
	}
	
	public void stopServers()
	{
		setServerIsRunning(false);
		
		getPeerServer().stopServer();
		getFileServer().stopServer();
	}

	private void refreshLocalHostFileList()
	{
		Thread lookUpFilesFromLocalHostThread = new Thread()
		{
			@Override
			public void run()
			{				
				while(isServerIsRunning())
				{
					try 
					{
						sleep(60000);
						
						if(isServerIsRunning())
						{
							List<PeerFile> newFileList = new ArrayList<PeerFile>();
							newFileList = makeLocalHostFileList();
							
							setLocalHostFiles(newFileList);
							localHost.setPeerFiles(newFileList);
								
							logger.info("LocalHost counts " + localHostFiles.size() + " files.");
								
							peerServer.localHostUpdatedFiles();
						}
					}
					catch (InterruptedException e) 
					{
						e.printStackTrace();
					}
				}
			}
		};
		
		lookUpFilesFromLocalHostThread.start();
	}
	
	private List<PeerFile> getLocalHostFileList()
	{
		setLocalHostFiles(makeLocalHostFileList());
		logger.info("LocalHost counts " + localHostFiles.size() + " files.");
		
		return localHostFiles;
	}
	
	private List<PeerFile> tempFileList;
	private List<PeerFile> makeLocalHostFileList()
	{
		File sharedDir = new File(Settings.getSharedDir());
		tempFileList = new ArrayList<PeerFile>();
		countForFiles = 0;
		
		try 
		{
			fillFileList(sharedDir);
		} 
		catch (UnknownHostException e) 
		{
			//When there are no files in sharedDir we will get an exception
		}
		
		return tempFileList;
	}
	
	private void fillFileList(File sharedDir) throws UnknownHostException
	{
		if(sharedDir.exists() && !sharedDir.isDirectory())
		{
			tempFileList.add(new PeerFile(countForFiles, sharedDir.getName(), sharedDir.length(), sharedDir.getAbsoluteFile().toString(), InetAddress.getLocalHost().getHostAddress().toString()));
			countForFiles++;
		} 
		else if(sharedDir.isDirectory())
		{
			String[] subSharedDirs = sharedDir.list();
			for (String shared : subSharedDirs) 
			{
				fillFileList(new File(sharedDir, shared));
			}
		}
	}
	
	public void localHostUpdateFiles()
	{
		localHost.setPeerFiles(getLocalHostFileList());
		peerServer.localHostUpdatedFiles();
	}
	
	public List<PeerFile> getFilesOfAllPeers()
	{
		List<PeerFile> allPeerFiles = new LinkedList<PeerFile>();
		
	    Iterator<Entry<InetAddress, Peer>> it = getPeers().entrySet().iterator();
	    while (it.hasNext()) 
	    {
	        Map.Entry<InetAddress, Peer> pairs = it.next();
	        allPeerFiles.addAll(pairs.getValue().getPeerFiles());
	    }
		
	    Collections.sort(allPeerFiles, new Comparator<PeerFile>() 
	    {
	    	public int compare(PeerFile peerFile1, PeerFile peerFile2)
	    	{
	    		String name1 = peerFile1.getFileName().toLowerCase();
	    		String name2 = peerFile2.getFileName().toLowerCase();
	    		
	    		return name1.compareTo(name2);
	    	}
		});
	    
	    return allPeerFiles;
	}
	
	public List<DownloadFile> getFilesOfDownloads()
	{
		List<DownloadFile> allDownloadFiles = new LinkedList<DownloadFile>();
		allDownloadFiles.addAll(getDownloadFiles());
		
		Collections.sort(allDownloadFiles, new Comparator<DownloadFile>() 
	    {
	    	public int compare(DownloadFile downloadFile1, DownloadFile downloadFile2)
	    	{
	    		String name1 = downloadFile1.getFileName().toLowerCase();
	    		String name2 = downloadFile2.getFileName().toLowerCase();
	    		
	    		return name1.compareTo(name2);
	    	}
		});
	    
	    return allDownloadFiles;
	}
	
	public List<Peer> getAllPeers()
	{
		List<Peer> peerList = new ArrayList<Peer>();
		for (Peer peer : peers.values()) {
			peerList.add(peer);
		}
		
		Collections.sort(peerList, new Comparator<Peer>() 
			    {
			    	public int compare(Peer peer1, Peer peer2)
			    	{
			    		String name1 = peer1.getPeerName().toLowerCase();
			    		String name2 = peer2.getPeerName().toLowerCase();
			    		
			    		return name1.compareTo(name2);
			    	}
				});
		
		return peerList;
	}
	
	public PeerFile getPeerFileWithId(int fileId)
	{
		for (PeerFile peerFile : localHostFiles) 
		{
			if(peerFile.getFileId() == fileId)
				return peerFile;
		}
		
		return null;
	}
	
	public void downloadPeerFile(PeerFile peerFile)
	{	
		if (!getDownloadedPeerFiles().contains(peerFile))
		{
			downloadedPeerFiles.add(peerFile);
			
			DownloadFile currentDownload = new DownloadFile(peerFile.getFileName(), peerFile.getFileSize(), peerFile.getPeerIp(), 0, "Starting");
			downloadFiles.add(currentDownload);
			logger.info(peerFile.getFileName() + " downloaded");
			notifyObservers();
			
			fileServer.dowloadFile(peerFile, currentDownload);
		}
	}
	
	public void fileDidNotDownlaodSuccessfull(PeerFile peerfile)
	{
		downloadedPeerFiles.remove(peerfile);
	}
	
	public void removeDownloadFile(DownloadFile downloadFile)
	{
		downloadFiles.remove(downloadFile);
		logger.info("Downloadfile " + downloadFile.getFileName() + " removed");
		notifyObservers();
	}
	
	public void addPeer(Peer peer)
	{
		peers.put(peer.getIpAddress(), peer);
		logger.info("Peer " + peer.getPeerName() + " added to list of peers");
		notifyObservers();
	}
	
	public void removePeer(Peer peer)
	{
		peers.remove(peer.getIpAddress());
		logger.info("Peer " + peer.getPeerName() + " deleted from list of peers");
		notifyObservers();
	}
	
	public Peer getPeer(InetAddress peerIp)
	{
		return peers.get(peerIp);
	}
	
	@Override
	public void addObserver(Observer o) {
		observers.add(o);	
	}

	@Override
	public void notifyObservers() 
	{
		for(Observer o : observers)
			o.update();		
	}

	/**
	 * 
	 * Getters & Setters
	 * 
	 */
	
	public BestandenListModel getBestandenListModel()
	{
		return bestandListModel;
	}
	
	public PeersListModel getPeerListModel()
	{
		return peerListModel;
	}
	
	public DownloadsTableModel getDownloadsTableModel()
	{
		return downloadTableModel;
	}
	
	public HashMap<InetAddress, Peer> getPeers() {
		return peers;
	}

	public void setPeers(HashMap<InetAddress, Peer> peers) {
		this.peers = peers;
	}

	public Peer getLocalHost() {
		return localHost;
	}

	public void setLocalHost(Peer localHost) {
		this.localHost = localHost;
	}
	
	public PeerServer getPeerServer() {
		return peerServer;
	}
	
	public FileServer getFileServer() {
		return fileServer;
	}

	public List<PeerFile> getLocalHostFiles() {
		return localHostFiles;
	}

	public void setLocalHostFiles(List<PeerFile> localHostFiles) {
		this.localHostFiles = localHostFiles;
	}

	public List<DownloadFile> getDownloadFiles() {
		return downloadFiles;
	}

	public void setDownloadFiles(List<DownloadFile> downloadFiles) {
		this.downloadFiles = downloadFiles;
	}

	public HashSet<PeerFile> getDownloadedPeerFiles() {
		return downloadedPeerFiles;
	}

	public void setDownloadedPeerFiles(HashSet<PeerFile> downloadedPeerFiles) {
		this.downloadedPeerFiles = downloadedPeerFiles;
	}
	
	public boolean isServerIsRunning() {
		return serverIsRunning;
	}

	public void setServerIsRunning(boolean serverIsRunning) {
		this.serverIsRunning = serverIsRunning;
	}
}
